﻿using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SharpCompress.Archives;
using SharpCompress.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Domain;
using Volo.Abp.Domain.Services;
using WorkFlowCore.Common.SimplePluginLoaders;
using WorkFlowCore.Workflows;
using WorkFlowCore.IRepositories;
using System.Threading;
using System.Reflection;
using Newtonsoft.Json;
using Confluent.Kafka;
using SharpCompress.Factories;
using WorkFlowCore.Common.Utils;

namespace WorkFlowCore.Plugins
{
    public class PluginManager : IDomainService
    {
        private readonly IBasicRepository<PluginApplyConfig, long> applyConfigRepository;
        private readonly IOptions<PluginOptions> pluginOptions;
        private readonly ILogger<PluginManager> logger;
        private static bool isPluginsLoaded = false;
        private static Dictionary<PluginType, List<ManifestWithConfig>> allPlugins;

        private static Dictionary<string, object> pluginCache;

        static PluginManager()
        {
            allPlugins = new Dictionary<PluginType, List<ManifestWithConfig>>();
            pluginCache = new Dictionary<string, object>();
        }

        public PluginManager(IBasicRepository<PluginApplyConfig, long> applyConfigRepository, IOptions<PluginOptions> pluginOptions, ILogger<PluginManager> logger)
        {
            this.applyConfigRepository = applyConfigRepository;
            this.pluginOptions = pluginOptions;
            this.logger = logger;
        }


        private static object ActivatorWithCache(string className,string configValue, Func<string, object> activator)
        {
            var key = Encrypt.MD5Encrypt32(className + configValue);
            lock ("pluginCache")
            {
                if (!pluginCache.ContainsKey(key))
                {
                    lock ("pluginCache")
                    {
                        if (!pluginCache.ContainsKey(key))
                        {
                            var obj = activator.Invoke(className);
                            if(obj!= null)
                            {
                                pluginCache.Add(key, obj);    
                            }
                        }
                    }
                }
            }
            return pluginCache[key];
        }

        public static void RegisterPluginType(Type type, string name)
        {
            if (allPlugins.Keys.Any(k => k.ExecutableType == type)) return;
            allPlugins.Add(new PluginType { ExecutableType = type, Name = name }, new List<ManifestWithConfig>());
        }

        public async Task<(List<PluginApplyConfig> applyConfigs, IReadOnlyList<ManifestWithConfig> manifests)> GetPluginApplyConfigs(string executableTypeFullName)
        {
            var configs = await applyConfigRepository.GetListAsync(a => a.ExecutableTypeFullName == executableTypeFullName);
            var manifests = GetPluginManifests(executableTypeFullName);
            return (configs, manifests);
        }

        private static List<ManifestWithConfig> GetPluginManifests(Type type)
        {
            var pluginsType = allPlugins.Keys.FirstOrDefault(k => k.ExecutableType.IsAssignableFrom(type));
            if (pluginsType == null) return null;

            return allPlugins.GetValueOrDefault(pluginsType);
        }

        public List<ManifestWithConfig> GetPluginManifests()
        {
            return allPlugins.SelectMany(p => p.Value).ToList();
        }

        public void LoadPluginsRoot()
        {
            var basePath = pluginOptions.Value.BasePath;
            if (isPluginsLoaded) return;
            lock ("LoadPlugins")
            {
                LoadPlugins(basePath);
                isPluginsLoaded = true;
            }
        }

        public IReadOnlyList<PluginType> GetPluginTypes()
        {
            return allPlugins.Keys.ToList();
        }

        public IReadOnlyList<ManifestWithConfig> GetPluginManifests(string executableTypeFullName)
        {
            var pluginsType = allPlugins.Keys.FirstOrDefault(k => k.ExecutableTypeFullName == executableTypeFullName);
            if (pluginsType == null) return null;
            return allPlugins.GetValueOrDefault(pluginsType);
        }

        public async Task AddOrUpdateConfig(IEnumerable<PluginApplyConfig> pluginApplyConfigs)
        {
            var currentConfigs = await applyConfigRepository.GetListAsync();

            foreach (var oldConfig in currentConfigs)
            {
                var newConfig = pluginApplyConfigs.FirstOrDefault(c => c.Id == oldConfig.Id);
                if (newConfig != null)
                {
                    oldConfig.Update(newConfig.Name, newConfig.ExecutableTypeFullName, newConfig.EntryFullName, newConfig.ConfigValue, newConfig.Enabled, newConfig.Order,newConfig.Description);
                    await applyConfigRepository.UpdateAsync(oldConfig);
                }
                else
                {
                    //await applyConfigRepository.DeleteAsync(oldConfig);
                }
            }

            var newConfigs = pluginApplyConfigs.Where(c => c.Id == 0);
            if (newConfigs.Any())
            {
                await applyConfigRepository.InsertManyAsync(newConfigs);
            }
        }

        public async Task<IEnumerable<PluginApplyConfig>> GetPluginApplyConfigs()
        {
            var currentConfigs = await applyConfigRepository.GetListAsync();
            var availableEntries = GetPluginTypes().Select(t => t.ExecutableTypeFullName);
            return currentConfigs.Where(c => availableEntries.Contains(c.ExecutableTypeFullName)).OrderBy(c => c.Order);
        }

        public T Resolve<T>(string pluginId)
        {
            string executableTypeFullName = typeof(T).FullName;
            var pluginManifests = GetPluginManifests(executableTypeFullName);

            var idInfo = pluginId.Split(':');
            var manifest = pluginManifests.FirstOrDefault(p => p.ClassName == idInfo[0]);

            object plugin = null;
            if (manifest != null)
            {
                PluginApplyConfig config = null;
                if(idInfo.Length > 1)
                {
                    config = applyConfigRepository.GetAsync(long.Parse(idInfo[1])).Result;
                }

                //plugin = ActivatorWithCache(manifest.ClassName, config?.ConfigValue, className =>
                //{
                //    var type = manifest.Assembly.GetType(manifest.ClassName);
                //    return Activator.CreateInstance(type);
                //});
                var type = manifest.Assembly.GetType(manifest.ClassName);
                plugin = Activator.CreateInstance(type);

                if (plugin is IConfigurable)
                {
                    var executable = (IConfigurable)plugin;
                    executable.Configure(new ConfigureContext
                    {
                        Parameters = DeserializeParemeters(config.ConfigValue),
                        OriginalParameters = config.ConfigValue
                    });
                }
            }
            return (T)plugin;
        }

        public T[] ResolveAll<T>()
        {
            string executableTypeFullName = typeof(T).FullName;
            var configs = GetPluginApplyConfigs(executableTypeFullName).Result;
            var pluginManifests = configs.manifests;

            var appConfigs = configs.applyConfigs.Where(c=>c.Enabled);
            var services = new List<T>();
            foreach (var appConfig in appConfigs)
            {
                var idInfo = appConfig.PluginId.Split(':');
                var manifest = pluginManifests.FirstOrDefault(p => p.ClassName == idInfo[0]);

                object plugin = null;
                if (manifest != null)
                {
                    //plugin = ActivatorWithCache(manifest.ClassName, appConfig.ConfigValue, className =>
                    //{
                    //    var type = manifest.Assembly.GetType(manifest.ClassName);
                    //    return Activator.CreateInstance(type);
                    //});
                    var type = manifest.Assembly.GetType(manifest.ClassName);
                    plugin = Activator.CreateInstance(type);
                }
                if (plugin is IConfigurable)
                {
                    var executable = (IConfigurable)plugin;
                    executable.Configure(new ConfigureContext
                    {
                        Parameters = DeserializeParemeters(appConfig.ConfigValue),
                        OriginalParameters = appConfig.ConfigValue
                    });
                }
                services.Add((T)plugin);
            }
            return services.ToArray();
        }


        private Dictionary<string, string> DeserializeParemeters(string config)
        {
            var values = new Dictionary<string, string>();
            try
            {
                config = config ?? "";
                var lines = config.Split(new string[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var line in lines)
                {
                    var eqIndex = line.IndexOf('=');
                    var key = line.Substring(0, eqIndex).Trim();
                    var value = line.Substring(eqIndex + 1).Trim();
                    if (values.ContainsKey(key))
                    {
                        values[key] = $"{values[key]},{value}";
                    }
                    else
                    {
                        values.Add(key, value);
                    }
                }
            }
            catch (Exception)
            {//
            }
            return values;
        }



        private static void LoadPlugins(string dir)
        {
            if (!Directory.Exists(dir)) return;
            var dirs = Directory.GetDirectories(dir);

            foreach (var _dir in dirs)
            {
                SimplePluginLoader.LoadPlugins<ManifestWithConfig>(_dir, configs =>
                {
                    foreach (var config in configs)
                    {
                        if (string.IsNullOrWhiteSpace(config.ClassName))
                        {
                            var types = config.Assembly.GetTypes();
                            foreach (var item in types)
                            {
                                Register(new ManifestWithConfig(item.FullName, config.Name ?? item.Name, config.Description, config.Entry, config.Version, config.Assembly));
                            }
                        }
                        else if (!Register(config)) continue;
                    }

                });

                LoadPlugins(_dir);
            }
        }

        public static bool Register(ManifestWithConfig config)
        {

            var type = config.Assembly.GetType(config.ClassName);
            if (type != null)
            {
                var allManifests = GetPluginManifests(type);
                if (allManifests != null)
                {
                    config.Configurable = typeof(IConfigurable).IsAssignableFrom(type);

                    var desAttrs = type.GetCustomAttributes<PluginDescriptionAttribute>();
                    if (desAttrs != null)
                    {
                        config.Description = $"{config.Description}\n{string.Join("\n", desAttrs.Select(d => d.Content))}";
                    }

                    allManifests.Add(config);
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 在初始化的时候，自动配置系统注册的插件到配置管理，以保证能查出来
        /// </summary>
        /// <returns></returns>
        public async Task AutoConfigureDefaultPlugins()
        {
            var pluginApplyConfigs = new List<PluginApplyConfig>();
            foreach (var configs in allPlugins)
            {
                foreach (var config in configs.Value)
                {
                    var currents = await applyConfigRepository.GetListAsync(a => a.ExecutableTypeFullName == configs.Key.ExecutableTypeFullName && a.EntryFullName == config.ClassName);
                    if (!currents.Any())
                    {
                        pluginApplyConfigs.Add(new PluginApplyConfig(0, config.Name, configs.Key.ExecutableTypeFullName, config.ClassName, string.Empty, true, 0,""));
                    }
                }
            }
            await applyConfigRepository.InsertManyAsync(pluginApplyConfigs);
        }
    }
}
